/* Copyright (C) 2014-2016 Dan Chapman <dpniel@ubuntu.com>

   This file is part of Dekko email client for Ubuntu Devices/

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of
   the License or (at your option) version 3

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "OnlineAccountsService.h"
#include <QDebug>
#include <QWeakPointer>
#include <QSettings>
#include <QGuiApplication>
#include <accounts-qt5/Accounts/Manager>
#include "app/Settings/Settings.h"
#include "configure.cmake.h"

static QWeakPointer<Accounts::Manager> sharedOAManager;
QSharedPointer<Accounts::Manager> OASharedManager::instance()
{
    QSharedPointer<Accounts::Manager> manager = sharedOAManager.toStrongRef();
    if (manager.isNull()) {
        manager = QSharedPointer<Accounts::Manager>(new Accounts::Manager);
        sharedOAManager = manager;
    }
    return manager;
}

OnlineAccountsService::OnlineAccountsService(QObject *parent) :
    QObject(parent)
{
    m_oaManager = OASharedManager::instance();
    connect(m_oaManager.data(), SIGNAL(accountCreated(Accounts::AccountId)), this, SLOT(handleAccountCreated(Accounts::AccountId)));
    connect(m_oaManager.data(), SIGNAL(accountRemoved(Accounts::AccountId)), this, SLOT(handleAccountRemoved(Accounts::AccountId)));
    m_application = m_oaManager->application(QStringLiteral("dekko.dekkoproject_dekko"));
    qDebug() << "MANAGER NULL? " << m_oaManager.isNull() << "ACCOUNT LIST: " << m_oaManager.data()->accountList();
    connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(handleAppStateChanged(Qt::ApplicationState)));
}

OnlineAccountsService::~OnlineAccountsService()
{
    qDeleteAll(m_allServices);
    qDeleteAll(m_allowedServices);
    qDeleteAll(m_localAccounts);
    m_allServices.clear();
    m_allowedServices.clear();
    m_localAccounts.clear();
}

bool OnlineAccountsService::checkAccountStillEnabled(const uint &accountId)
{
    bool ret = false;
    Q_FOREACH(Accounts::AccountService *s, getAllowedServices()) {
        qDebug() << __FILE__ << __func__ << "ServiceID: " << s->account()->id() << " accountId: " << accountId;
        if (s->account()->id() == accountId) {
            ret = true;
            break;
        }
    }
    return ret;
}

bool OnlineAccountsService::checkAccountStillExists(const uint &accountId)
{
    bool ret = false;
    Q_FOREACH(Accounts::AccountService *s, getAllServices()) {
        qDebug() << __FILE__ << __func__ << "ServiceID: " << s->account()->id() << " accountId: " << accountId;
        if (s->account()->id() == accountId) {
            ret = true;
            break;
        }
    }
    return ret;
}

void OnlineAccountsService::handleAccountCreated(Accounts::AccountId accountId)
{
    Accounts::Account *account = m_oaManager->account(accountId);
    if (account->providerName() != QStringLiteral("google")) {
        return; // Were only interested in google accounts atm.
    }
    // check for allowed services
    Q_FOREACH(Accounts::Service service, account->services()) {
        if (m_application.isValid()) {
            if (m_application.serviceUsage(service).isEmpty()) {
                qDebug() << "AccountCreated but service not allowed: " << service.displayName();
                continue;
            } else {
                qDebug() << "AccountCreated and Service Allowed: " << service.displayName();
                Accounts::AccountService *s = new Accounts::AccountService(account, service);
                if (s->enabled() && !checkForLocalAccount(s)) {
                    qDebug() << "AccountCreated SERVICE ENABLED";
                    emit newAccountEnabled(accountId);
                } else {
                    if (checkForLocalAccount(s)) {
                        emit accountEnabled(getDekkoAccountIdForService(s));
                    }
                    qDebug() << "AccountCreated SERVICE DISABLED";
                }
            }
        }
    }
    updateAllowedServices();
}

void OnlineAccountsService::handleAccountRemoved(Accounts::AccountId accountId)
{
    Q_FOREACH(Dekko::Accounts::Account *account, getDekkoAccounts()) {
        if (!account->isUoaAccount()) continue;
        Dekko::Settings::UOASettings *oas = static_cast<Dekko::Settings::UOASettings *>(account->uoaSettings());
        Q_ASSERT(oas);
        if (oas->uoaAccountId() == accountId) {
            emit accountRemoved(account->accountId()); // We need to emit this so we can fully remove account from storage
            break;
        }
    }
    updateAllowedServices();
}

void OnlineAccountsService::handleEnabledChanged(bool enabled)
{
    Accounts::AccountService *service = qobject_cast<Accounts::AccountService *>(sender());
    if (service->enabled() != enabled) {
        // then it lied to us! this can't really happen but let's still guard it
        return;
    }
    /* If enabled then
     *      loop through current accounts & check if we have a config for this
     *          if (yes) then create and append to the accounts manager
     *          else: notify for further configureation
     * else if not enabled
     *      check if account is configred and is currently running
     *          if (yes) then disable and remove
     *          else: do nothing
     */

    if (enabled) {
        // check if we have a config for this account
        if (checkForLocalAccount(service)) {
            // Ok so this just needs to be opened in the accountsManager
            emit accountEnabled(getDekkoAccountIdForService(service));
        } else {
            // This means the account was created in USSOA and dekko was enabled
            // as a consumer, but we don't have the extra info we need locally
            // So this signal will indicate that the setupWizard needs to run
            emit newAccountEnabled(service->account()->id());
        }
    } else {
        if (checkForLocalAccount(service)) {
            emit accountDisabled(getDekkoAccountIdForService(service));
        }
    }
    updateAllowedServices();
}

void OnlineAccountsService::updateAllowedServices()
{
    qDeleteAll(m_allServices);
    qDeleteAll(m_allowedServices);
    qDeleteAll(m_localAccounts);
    m_allServices.clear();
    m_allowedServices.clear();
    m_localAccounts.clear();

    Q_FOREACH(Accounts::AccountService *service, getAllServices()) {
        if (service->enabled()) {
            if (checkForLocalAccount(service)) {
                qDebug() << "ACCOUNT ENABLED: " << getDekkoAccountIdForService(service);
                emit accountEnabled(getDekkoAccountIdForService(service));
            } else {
                qDebug() << "NEW ACCOUNT ENABLED!";
                emit newAccountEnabled(service->account()->id());
            }
        } else {
            if (checkForLocalAccount(service)) {
                qDebug() << "ACCOUNT DISABLED: " << getDekkoAccountIdForService(service);
                emit accountDisabled(getDekkoAccountIdForService(service));
            } else {
                qDebug() << "WHAT DO WE DO HERE??" << service->account()->displayName();
            }
        }
    }
//    watchServices(m_allServices);
}

void OnlineAccountsService::handleAppStateChanged(Qt::ApplicationState state)
{
    switch (state) {
    case Qt::ApplicationActive:
        qDebug() << "APPLICATION ACTIVE";
        updateAllowedServices();
        break;
    case Qt::ApplicationSuspended:
        qDebug() << "APP SUSPENDED";
        break;
    case Qt::ApplicationHidden:
        qDebug() << "APPLICATION HIDDEN";
        break;
    case Qt::ApplicationInactive:
        qDebug() << "APPLICATION INACTIVE";
        // On unity8 all our service pointers we are watching
        // will get invalidated when we go to the background
        // Let's just clear them and pick up the changes
        // on updateAllowedServices() when we become active again.
#ifdef CLICK_MODE
        qDeleteAll(m_allServices);
        qDeleteAll(m_allowedServices);
        qDeleteAll(m_localAccounts);
        m_allServices.clear();
        m_allowedServices.clear();
        m_localAccounts.clear();
#endif
        break; // do nothing
    }
}

void OnlineAccountsService::watchServices(OnlineAccountsService::OAAccountServiceList serviceList)
{
    Q_FOREACH(Accounts::AccountService *s, serviceList) {
        connect(s, SIGNAL(enabled(bool)), this, SLOT(handleEnabledChanged(bool)));
    }
}

bool OnlineAccountsService::checkForLocalAccount(Accounts::AccountService *service)
{
    bool isLocal = false;
    Q_FOREACH(Dekko::Accounts::Account *account, getDekkoAccounts()) {
        if (!account->isUoaAccount()) continue;
        Dekko::Settings::UOASettings *oas = static_cast<Dekko::Settings::UOASettings *>(account->uoaSettings());
        Q_ASSERT(oas);
        if (oas->uoaAccountId() == service->account()->id()) {
            isLocal = true;
            break;
        }
    }
    return isLocal;
}

QString OnlineAccountsService::getDekkoAccountIdForService(Accounts::AccountService *service)
{
    QString accountId;
    Q_FOREACH(Dekko::Accounts::Account *account, getDekkoAccounts()) {
        if (!account->isUoaAccount()) continue;
        Dekko::Settings::UOASettings *oas = static_cast<Dekko::Settings::UOASettings *>(account->uoaSettings());
        Q_ASSERT(oas);
        if (oas->uoaAccountId() == service->account()->id()) {
            accountId = account->accountId();
            break;
        }
    }
    return accountId;
}

OnlineAccountsService::DekkoAccountList OnlineAccountsService::getDekkoAccounts()
{
    // We have to work from qsettings here as the accountsmanager only contains
    // live accounts, so disabled online accounts will not get loaded.
    QSettings settings;
    QStringList groups;
    settings.beginGroup(QStringLiteral("Accounts"));
    groups = settings.childGroups();
    settings.endGroup();
    if (!groups.length()) {
        return DekkoAccountList();
    }
    qDeleteAll(m_localAccounts);
    m_localAccounts.clear();

    Q_FOREACH(const QString &group, groups) {
        Dekko::Accounts::Account *account = new Dekko::Accounts::Account(this);
        account->setAccountId(group);
        if (account->isUoaAccount()) {
            qDebug() << "LOCAL ACCOUNT FOUND: " << account->accountId();
            m_localAccounts.append(account);
        }
    }
    return m_localAccounts;
}

OnlineAccountsService::OAAccountServiceList OnlineAccountsService::getAllowedServices()
{
    Q_FOREACH(Accounts::AccountId id, m_oaManager->accountList()) {
        Accounts::Account *account = m_oaManager->account(id);
        if (account->providerName() == QStringLiteral("google")) {
            Q_FOREACH(Accounts::Service service, account->services()) {
                if (m_application.isValid()) {
                    if (!m_application.serviceUsage(service).isEmpty()) {
                        qDebug() << "Service: " << service.displayName();
                        Accounts::AccountService *s = new Accounts::AccountService(account, service);
                        if (s->enabled()) {
                            m_allowedServices.append(s);
                        }
                    }
                }
            }
        }
    }
    return m_allowedServices;
}

OnlineAccountsService::OAAccountServiceList OnlineAccountsService::getAllServices()
{
    Q_FOREACH(Accounts::AccountId id, m_oaManager->accountList()) {
        Accounts::Account *account = m_oaManager->account(id);
        if (account->providerName() == QStringLiteral("google")) {
            Q_FOREACH(Accounts::Service service, account->services()) {
                if (m_application.isValid()) {
                    if (!m_application.serviceUsage(service).isEmpty()) {
                        qDebug() << "Service: " << service.displayName();
                        m_allServices.append(new Accounts::AccountService(account, service));
                    }
                }
            }
        }
    }
    return m_allServices;
}



